﻿using System;
using System.Collections.Generic;
using System.Text;
using Pixysoft.Framework.Verifications;

namespace Pixysoft.Framework.Noebe.Orm
{
    /// <summary>
    /// UNSUPPORT:
    /// objectcollection.value
    /// valuecollection.value
    /// 
    /// 如果是值类型集合，不需要添加.Value的默认结尾
    /// 
    /// stack:左到右入栈，右到左出栈。先出子
    /// queue: 左到右入栈，左到右出栈。先出root
    /// </summary>
    class OrmQueryStructureFactory
    {
        /// <summary>
        /// 对关系进行过滤 找出关系重复的并标记状态
        /// </summary>
        /// <param name="relations"></param>
        /// <param name="conditions"></param>
        public static void RegularRelationQueue(Queue<OrmRelation> relations, List<string> conditions)
        {
            foreach (string condition in conditions)
            {
                int index = 0;

                foreach (OrmRelation relation in relations)
                {
                    string[] conditionRelations = condition.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);

                    //如果超出数组 则停止

                    if (index > conditionRelations.Length - 1)
                        break;

                    //如果发现属性相同，则可忽略 继续

                    if (relation.FieldName.Equals(conditionRelations[index++], StringComparison.OrdinalIgnoreCase))
                    {
                        relation.Ignore = true;

                        continue;
                    }

                    switch (relation.FieldDotNetType)
                    {
                        case DotNetPropertyType.Binary:
                        case DotNetPropertyType.Value:
                        case DotNetPropertyType.Enum:
                            relation.Ignore = true;
                            continue;
                        default:
                            break;
                    }
                }

            }
        }

        /// <summary>
        /// 创建逆序堆栈
        /// </summary>
        public static Stack<OrmRelation> CreateStack(Type rootType, string queryString)
        {
            return ScriptToStack(rootType, queryString);
        }
        /// <summary>
        /// 创建正序队列
        /// </summary>
        /// <param name="rootType"></param>
        /// <param name="queryString"></param>
        /// <returns></returns>
        public static Queue<OrmRelation> CreateQueue(Type rootType, string queryString)
        {
            return ScriptToQueue(rootType, queryString);
        }

        /// <summary>
        /// 构造正序队列
        /// </summary>
        /// <param name="rootType"></param>
        /// <param name="queryString"></param>
        /// <returns></returns>
        private static Queue<OrmRelation> ScriptToQueue(Type rootType, string queryString)
        {
            List<OrmRelation> list = InitializeRelationList(rootType, queryString);

            Queue<OrmRelation> queue = new Queue<OrmRelation>();

            OrmRelation formerRelation = null;

            foreach (OrmRelation relation in list)
            {
                if (formerRelation == null)
                {
                    switch (relation.FieldDotNetType)
                    {
                        case DotNetPropertyType.Enum:
                        case DotNetPropertyType.Value:
                        case DotNetPropertyType.Binary:
                            {
                                relation.Relation = OrmRelationCollection.Base2Value;
                                break;
                            }

                        case DotNetPropertyType.Interface:
                        case DotNetPropertyType.Object:
                            {
                                relation.Relation = OrmRelationCollection.Base2Object;
                                break;
                            }

                        case DotNetPropertyType.ObjectArray:
                        case DotNetPropertyType.InterfaceArray:
                        case DotNetPropertyType.InterfaceList:
                        case DotNetPropertyType.ObjectList:
                            {
                                relation.Relation = OrmRelationCollection.Base2ObjectCollection;
                                break;
                            }

                        case DotNetPropertyType.BinaryList:
                        case DotNetPropertyType.ValueList:
                        case DotNetPropertyType.EnumList:
                        case DotNetPropertyType.ValueArray:
                        case DotNetPropertyType.EnumArray:
                        default:
                            {
                                relation.Relation = OrmRelationCollection.Base2ValueCollection;
                                break;
                            }
                    }
                }
                else
                {
                    switch (formerRelation.FieldDotNetType)
                    {
                        case DotNetPropertyType.Interface:
                        case DotNetPropertyType.Object:
                            {
                                switch (relation.FieldDotNetType)
                                {
                                    case DotNetPropertyType.Interface:
                                    case DotNetPropertyType.Object:
                                        {
                                            relation.Relation = OrmRelationCollection.Object2Object;
                                            break;
                                        }

                                    case DotNetPropertyType.ObjectArray:
                                    case DotNetPropertyType.InterfaceArray:
                                    case DotNetPropertyType.InterfaceList:
                                    case DotNetPropertyType.ObjectList:
                                        {
                                            relation.Relation = OrmRelationCollection.Object2ObjectCollection;
                                            break;
                                        }


                                    case DotNetPropertyType.Enum:
                                    case DotNetPropertyType.Value:
                                    case DotNetPropertyType.BinaryList:
                                    case DotNetPropertyType.Binary:
                                        {
                                            relation.Relation = OrmRelationCollection.Object2Value;
                                            break;
                                        }

                                    case DotNetPropertyType.ValueList:
                                    case DotNetPropertyType.EnumList:
                                    case DotNetPropertyType.ValueArray:
                                    case DotNetPropertyType.EnumArray:
                                        {
                                            relation.Relation = OrmRelationCollection.Object2ValueCollection;
                                            break;
                                        }

                                    default:
                                        {
                                            throw Exceptions.UnexpectedResultException(
                                                string.Format("error in script. parentType:{0}, fieldName:{1}, fieldType:{2}",
                                                formerRelation.OrmType, formerRelation.FieldName, formerRelation.FieldDotNetType));
                                        }
                                }
                                break;
                            }

                        case DotNetPropertyType.ObjectArray:
                        case DotNetPropertyType.InterfaceArray:
                        case DotNetPropertyType.InterfaceList:
                        case DotNetPropertyType.ObjectList:
                            {
                                switch (relation.FieldDotNetType)
                                {
                                    case DotNetPropertyType.Interface:
                                    case DotNetPropertyType.Object:
                                        {
                                            relation.Relation = OrmRelationCollection.ObjectCollection2Object;
                                            break;
                                        }

                                    case DotNetPropertyType.ObjectArray:
                                    case DotNetPropertyType.InterfaceArray:
                                    case DotNetPropertyType.InterfaceList:
                                    case DotNetPropertyType.ObjectList:
                                        {
                                            relation.Relation = OrmRelationCollection.ObjectCollection2ObjectCollection;
                                            break;
                                        }


                                    case DotNetPropertyType.ValueList:
                                    case DotNetPropertyType.EnumList:
                                    case DotNetPropertyType.ValueArray:
                                    case DotNetPropertyType.EnumArray:
                                        {
                                            relation.Relation = OrmRelationCollection.ObjectCollection2ValueCollection;
                                            break;
                                        }

                                    case DotNetPropertyType.Enum:
                                    case DotNetPropertyType.Value:
                                    case DotNetPropertyType.BinaryList:
                                    case DotNetPropertyType.Binary:
                                    default:
                                        {
                                            relation.Relation = OrmRelationCollection.ObjectCollection2Value;
                                            break;
                                        }
                                }
                                break;
                            }

                        case DotNetPropertyType.BinaryList:
                        case DotNetPropertyType.ValueList:
                        case DotNetPropertyType.EnumList:
                        case DotNetPropertyType.ValueArray:
                        case DotNetPropertyType.EnumArray:
                            {

                                throw Exceptions.UnexpectedResultException(
                                    string.Format("error in script. parentType:{0}, fieldName:{1}, fieldType:{2}",
                                    formerRelation.OrmType, formerRelation.FieldName, formerRelation.FieldDotNetType));
                            }

                        case DotNetPropertyType.Enum:
                        case DotNetPropertyType.Value:
                        case DotNetPropertyType.Binary:
                        default:
                            {
                                throw Exceptions.UnexpectedResultException(
                                    string.Format("error in script. parentType:{0}, fieldName:{1}, fieldType:{2}",
                                    formerRelation.OrmType, formerRelation.FieldName, formerRelation.FieldDotNetType));
                            }
                    }
                }

                formerRelation = relation;

                queue.Enqueue(relation);
            }

            return queue;
        }
        /// <summary>
        /// querystring must NOT start with root.
        /// object.FieldName = FieldType
        /// </summary>
        /// <param name="objType"></param>
        /// <param name="queryString"></param>
        /// <returns></returns>
        private static Stack<OrmRelation> ScriptToStack(Type ormType, string queryString)
        {
            List<OrmRelation> list = InitializeRelationList(ormType, queryString);

            Stack<OrmRelation> stack = new Stack<OrmRelation>();

            OrmRelation formerRelation = null;

            foreach (OrmRelation relation in list)
            {
                if (formerRelation == null)
                {
                    switch (relation.FieldDotNetType)
                    {
                        case DotNetPropertyType.Enum:
                        case DotNetPropertyType.Value:
                        case DotNetPropertyType.Binary:
                            {
                                relation.Relation = OrmRelationCollection.Base2Value;
                                break;
                            }

                        case DotNetPropertyType.Interface:
                        case DotNetPropertyType.Object:
                            {
                                relation.Relation = OrmRelationCollection.Base2Object;
                                break;
                            }

                        case DotNetPropertyType.ObjectArray:
                        case DotNetPropertyType.InterfaceArray:
                        case DotNetPropertyType.InterfaceList:
                        case DotNetPropertyType.ObjectList:
                            {
                                relation.Relation = OrmRelationCollection.Base2ObjectCollection;
                                break;
                            }

                        case DotNetPropertyType.BinaryList:
                        case DotNetPropertyType.ValueList:
                        case DotNetPropertyType.EnumList:
                        case DotNetPropertyType.ValueArray:
                        case DotNetPropertyType.EnumArray:
                        default:
                            {
                                relation.Relation = OrmRelationCollection.Base2ValueCollection;
                                break;
                            }
                    }
                }
                else
                {
                    switch (formerRelation.FieldDotNetType)
                    {
                        case DotNetPropertyType.Interface:
                        case DotNetPropertyType.Object:
                            {
                                switch (relation.FieldDotNetType)
                                {
                                    case DotNetPropertyType.Interface:
                                    case DotNetPropertyType.Object:
                                        {
                                            relation.Relation = OrmRelationCollection.Object2Object;
                                            break;
                                        }

                                    case DotNetPropertyType.ObjectArray:
                                    case DotNetPropertyType.InterfaceArray:
                                    case DotNetPropertyType.InterfaceList:
                                    case DotNetPropertyType.ObjectList:
                                        {
                                            relation.Relation = OrmRelationCollection.Object2ObjectCollection;
                                            break;
                                        }


                                    case DotNetPropertyType.Enum:
                                    case DotNetPropertyType.Value:
                                    case DotNetPropertyType.BinaryList:
                                    case DotNetPropertyType.Binary:
                                        {
                                            relation.Relation = OrmRelationCollection.Object2Value;
                                            break;
                                        }

                                    case DotNetPropertyType.ValueList:
                                    case DotNetPropertyType.EnumList:
                                    case DotNetPropertyType.ValueArray:
                                    case DotNetPropertyType.EnumArray:
                                        {
                                            relation.Relation = OrmRelationCollection.Object2ValueCollection;
                                            break;
                                        }

                                    default:
                                        {
                                            throw Exceptions.UnexpectedResultException(
                                                string.Format("error in script. parentType:{0}, fieldName:{1}, fieldType:{2}",
                                                formerRelation.OrmType, formerRelation.FieldName, formerRelation.FieldDotNetType));
                                        }
                                }
                                break;
                            }

                        case DotNetPropertyType.ObjectArray:
                        case DotNetPropertyType.InterfaceArray:
                        case DotNetPropertyType.InterfaceList:
                        case DotNetPropertyType.ObjectList:
                            {
                                switch (relation.FieldDotNetType)
                                {
                                    case DotNetPropertyType.Interface:
                                    case DotNetPropertyType.Object:
                                        {
                                            relation.Relation = OrmRelationCollection.ObjectCollection2Object;
                                            break;
                                        }

                                    case DotNetPropertyType.ObjectArray:
                                    case DotNetPropertyType.InterfaceArray:
                                    case DotNetPropertyType.InterfaceList:
                                    case DotNetPropertyType.ObjectList:
                                        {
                                            relation.Relation = OrmRelationCollection.ObjectCollection2ObjectCollection;
                                            break;
                                        }


                                    case DotNetPropertyType.ValueList:
                                    case DotNetPropertyType.EnumList:
                                    case DotNetPropertyType.ValueArray:
                                    case DotNetPropertyType.EnumArray:
                                        {
                                            relation.Relation = OrmRelationCollection.ObjectCollection2ValueCollection;
                                            break;
                                        }

                                    case DotNetPropertyType.Enum:
                                    case DotNetPropertyType.Value:
                                    case DotNetPropertyType.BinaryList:
                                    case DotNetPropertyType.Binary:
                                    default:
                                        {
                                            relation.Relation = OrmRelationCollection.ObjectCollection2Value;
                                            break;
                                        }
                                }
                                break;
                            }

                        case DotNetPropertyType.BinaryList:
                        case DotNetPropertyType.ValueList:
                        case DotNetPropertyType.EnumList:
                        case DotNetPropertyType.ValueArray:
                        case DotNetPropertyType.EnumArray:
                            {

                                throw Exceptions.UnexpectedResultException(
                                    string.Format("error in script. parentType:{0}, fieldName:{1}, fieldType:{2}",
                                    formerRelation.OrmType, formerRelation.FieldName, formerRelation.FieldDotNetType));
                            }

                        case DotNetPropertyType.Enum:
                        case DotNetPropertyType.Value:
                        case DotNetPropertyType.Binary:
                        default:
                            {
                                throw Exceptions.UnexpectedResultException(
                                    string.Format("error in script. parentType:{0}, fieldName:{1}, fieldType:{2}",
                                    formerRelation.OrmType, formerRelation.FieldName, formerRelation.FieldDotNetType));
                            }
                    }
                }

                formerRelation = relation;

                stack.Push(relation);
            }

            return stack;
        }
        /// <summary>
        /// 构造正序堆栈
        /// </summary>
        /// <param name="ormType"></param>
        /// <param name="queryString"></param>
        /// <returns></returns>
        private static List<OrmRelation> InitializeRelationList(Type ormType, string queryString)
        {
            List<OrmRelation> stack = new List<OrmRelation>();

            string[] scripts = queryString.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);

            if (scripts.Length < 1)
                throw Exceptions.UnexpectedResultException("error in script");

            Type currentOrmType = ormType;

            for (int i = 0; i < scripts.Length; i++)
            {
                string fieldName = scripts[i].Trim().ToUpper();

                OrmRelation relation = new OrmRelation(currentOrmType, fieldName);

                Type fieldType = null;

                switch (relation.FieldDotNetType)
                {
                    case DotNetPropertyType.Enum:
                    case DotNetPropertyType.Value:
                    case DotNetPropertyType.Binary:
                        {
                            fieldType = ReflectHelper.GetFieldObjectType(currentOrmType, fieldName);

                            break;
                        }

                    case DotNetPropertyType.Interface:
                    case DotNetPropertyType.Object:
                        {
                            fieldType = ReflectHelper.GetFieldObjectType(currentOrmType, fieldName);

                            break;
                        }


                    case DotNetPropertyType.BinaryList:
                    case DotNetPropertyType.ValueList:
                    case DotNetPropertyType.EnumList:
                        {
                            fieldType = ReflectHelper.GetFieldListObjectType(currentOrmType, fieldName);

                            if (i < scripts.Length - 1)
                                throw Exceptions.UnexpectedResultException("value collection do not have successor script.");

                            break;
                        }

                    case DotNetPropertyType.ValueArray:
                    case DotNetPropertyType.EnumArray:
                        {
                            fieldType = ReflectHelper.GetFieldArrayObjectType(currentOrmType, fieldName);

                            if (i < scripts.Length - 1)
                                throw Exceptions.UnexpectedResultException("value collection do not have successor script.");

                            break;
                        }

                    case DotNetPropertyType.ObjectArray:
                    case DotNetPropertyType.InterfaceArray:
                        {
                            fieldType = ReflectHelper.GetFieldArrayObjectType(currentOrmType, fieldName);

                            break;
                        }
                    case DotNetPropertyType.InterfaceList:
                    case DotNetPropertyType.ObjectList:
                        {
                            fieldType = ReflectHelper.GetFieldListObjectType(currentOrmType, fieldName);

                            break;
                        }

                    default:
                        {
                            throw Exceptions.UnexpectedResultException(string.Format("script error, unknown fieldname {0} for type {1}", fieldName, currentOrmType));
                        }
                }

                relation.FieldType = fieldType;

                stack.Add(relation);

                currentOrmType = fieldType;
            }
            return stack;
        }


        public static string DumpToString(Stack<OrmRelation> stack)
        {
            StringBuilder builder = new StringBuilder();

            while (stack.Count > 0)
            {
                OrmRelation relation = stack.Pop();

                builder.AppendFormat("[{0}]{1}.{2}->", relation.Relation, relation.OrmType.Name, relation.FieldName).AppendLine();
            }

            return builder.ToString();
        }

        public static string DumpToString(Queue<OrmRelation> queue)
        {
            StringBuilder builder = new StringBuilder();

            while (queue.Count > 0)
            {
                OrmRelation relation = queue.Dequeue();

                builder.AppendFormat("[{0}]{1}.{2}->", relation.Relation, relation.OrmType.Name, relation.FieldName).AppendLine();
            }

            return builder.ToString();
        }

        public class OrmRelation
        {
            bool ignore = false;//是否重复的关系 

            Type ormType;
            string fieldName;
            DotNetPropertyType fieldDotNetType;
            Type fieldType;
            OrmRelationCollection relation = OrmRelationCollection.Unknown;

            public OrmRelation(Type ormType, string fieldName)
            {
                this.ormType = ormType;
                this.fieldName = fieldName;
                this.fieldDotNetType = ReflectHelper.GetFieldObjectDotNetType(ormType, fieldName);
            }

            public OrmRelation(Type ormType, string fieldName, bool ignore)
            {
                this.ormType = ormType;
                this.fieldName = fieldName;
                this.fieldDotNetType = ReflectHelper.GetFieldObjectDotNetType(ormType, fieldName);
                this.ignore = ignore;
            }

            public Type OrmType
            {
                get { return ormType; }
            }
            public string FieldName
            {
                get { return fieldName; }
            }
            public DotNetPropertyType FieldDotNetType
            {
                get { return fieldDotNetType; }
            }
            public Type FieldType
            {
                get
                {
                    return this.fieldType;
                }
                set
                {
                    this.fieldType = value;
                }
            }
            public OrmRelationCollection Relation
            {
                get { return relation; }
                set { relation = value; }
            }
            public bool Ignore
            {
                get { return ignore; }
                set { this.ignore = value; }
            }
            public override string ToString()
            {
                StringBuilder builder = new StringBuilder();
                builder.AppendFormat("Type:{0} Field:{1} FieldType:{2} Relation:{3} Ignore:{4}", OrmType.Name, FieldName, fieldType.Name, relation, ignore);
                return builder.ToString();
            }
        }

        /// <summary>
        /// 查看两者关系
        /// </summary>
        public enum OrmRelationCollection
        {
            Base2Value,
            Object2Value,
            ObjectCollection2Value,

            Base2Object,
            Object2Object,
            ObjectCollection2Object,

            Base2ObjectCollection,
            Object2ObjectCollection,
            ObjectCollection2ObjectCollection,

            Base2ValueCollection,
            Object2ValueCollection,
            ObjectCollection2ValueCollection,

            Base,
            Unknown,
        }
    }
}
